package com.personalkeeper;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.DialogInterface.OnCancelListener;
import android.content.SharedPreferences.Editor;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.Cursor;
import android.graphics.Color;
import android.location.Location;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.personalkeeper.HistoryProvider.LocationHistory;
import com.personalkeeper.PersonalKeeper.Settings;
import com.personalkeeper.PersonalKeeper.Utils;
import static com.personalkeeper.PersonalKeeper.PACKAGE_NAME;

public abstract class ClientMapActivity extends MapActivity
    implements
    LocationServiceController.LocationListener,
    LocationServiceController.SMSListener,
    MarkersLayer.MarkerTapListener {

    private static final String TAG = ClientMapActivity.class.toString();

    private static final String ANIMATE_TO_CURRENT_POINT_KEY = PACKAGE_NAME + ".animatetocurrent";
    private static final String CENTRAL_POINT_LATITUDE_KEY = PACKAGE_NAME + ".centralpointlatitude";
    private static final String CENTRAL_POINT_LONGITUDE_KEY = PACKAGE_NAME + ".centralpointlongitude";


    private static class ToastViewHolder {
        public View toastRoot;
        public ImageView markerImage;
        public TextView markerTitle;
        public TextView markerSnippet;
    }

    private class AsyncPathLoader extends AsyncTask<Long, Void, ArrayList<Marker>> {

        private Dialog progressDialog;

        @Override
        protected void onPreExecute() {
            progressDialog = ProgressDialog.show(
                                 ClientMapActivity.this,
                                 getString(R.string.loading_path),
                                 getString(R.string.it_may_take_a_while),
                                 true,
                                 false,
            new OnCancelListener() {
                //
                @Override
                public void onCancel(DialogInterface dialog) {
                    AsyncPathLoader.this.cancel(true);
                    ClientMapActivity.this.finish();
                }
            });
        }

        @Override
        protected ArrayList<Marker> doInBackground(Long... params) {
            ArrayList<Marker> result = new ArrayList<Marker>();
            long startTime = params[0];
            Cursor c = getContentResolver().query(
                           LocationHistory.CONTENT_URI,
                           new String[] { LocationHistory.LATITUDE, LocationHistory.LONGITUDE, LocationHistory.DATE },
                           LocationHistory.DATE + " >= " + startTime,
                           null,
                           null);
            try {
                int latitudeID = c.getColumnIndexOrThrow(LocationHistory.LATITUDE);
                int longitudeID = c.getColumnIndexOrThrow(LocationHistory.LONGITUDE);
                int dateID = c.getColumnIndexOrThrow(LocationHistory.DATE);
                if (c.moveToFirst()) {
                    do {
                        double latitude = c.getDouble(latitudeID);
                        double longitude = c.getDouble(longitudeID);
                        long date = c.getLong(dateID);
                        String title = new Date(date).toString();
                        Marker marker = new Marker(Utils.createGeoPoint(latitude, longitude), title, "", Marker.TYPE_PASSED_POINT, ClientMapActivity.this);
                        result.add(marker);
                    } while (c.moveToNext() && !isCancelled());
                }
            } finally {
                c.close();
            }

            return result;
        }

        @Override
        protected void onPostExecute(ArrayList<Marker> result) {
            progressDialog.dismiss();
            for (Marker marker: result)
                marker.setAssotiateContext(ClientMapActivity.this);
            pathHolder.addRange(result);
            mMap.postInvalidate();
            Log.d(TAG, "path loading complete: number of points: " + result.size());
        }
    }



    private LocationServiceController mGpsServiceController;
    private MapView mMap;
    private List<Overlay> mMapOverlays;
    private MapController mMapController;
    private SharedPreferences mPreferences;
    private Location mLastUpdatedLocation;
    private boolean mAnimateToCurrentPoint;
    private float mMinDistance;
    private GeoPoint mCentralPoint;
    private ToastViewHolder toastView;
    private AsyncPathLoader mAsyncPathLoader;
    // overlays
    //private MarkersLayer currentPointMarker;
    private CurrentMarkerLayer currentPointMarker;
    private CircleOverlay accuracyOverlay;
    private PathHolder pathHolder;

    @Override
    protected void onStart() {
        super.onStart();
        mGpsServiceController.bindService();
        if (isDrawingPath()) {
            mGpsServiceController.bindService();
            mAsyncPathLoader = new AsyncPathLoader();
            mAsyncPathLoader.execute(mPreferences.getLong(Settings.TRACKING_START_TIME, System.currentTimeMillis()));
        }
        mGpsServiceController.setLocationListener(this);
        mGpsServiceController.setSMSListener(this);
    }
    @Override
    protected void onStop() {
        super.onStop();
        if (mAsyncPathLoader != null) {
            mAsyncPathLoader.cancel(true);
            mAsyncPathLoader = null;
        }
    }
    @Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.map_layout);
        mMap = (MapView) findViewById(R.id.mapview);
        mMap.setBuiltInZoomControls(true);
        mMapOverlays = mMap.getOverlays();
        mMapController = mMap.getController();

        // creating overlays
        MarkersLayer markersOnPathLayer =
            new MarkersLayer(getResources().getDrawable(R.drawable.passed));
        markersOnPathLayer.setOnTapListener(this);
        PolylineLayer pathLayer = new PolylineLayer(Color.BLUE);
        accuracyOverlay = new CircleOverlay(null);
        currentPointMarker = new CurrentMarkerLayer(getResources().getDrawable(R.drawable.current));
        currentPointMarker.setOnTapListener(this);

        pathHolder = new PathHolder(pathLayer, markersOnPathLayer, this);

        // adding overlays
        getMapOverlays().add(accuracyOverlay);
        getMapOverlays().add(currentPointMarker.getOverlay());
        getMapOverlays().add(pathLayer);
        getMapOverlays().add(markersOnPathLayer);

        mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        PreferenceManager.setDefaultValues(this, R.xml.preference_screen, false);
        int zoomLevel;
        try {
            mMinDistance = Settings.loadMinDistance(mPreferences);
            zoomLevel = Settings.loadInitialZoomValue(mPreferences);
        } catch (ClassCastException e) {
            Log.d(TAG, "failed to read configuration, setting all preferences to default values");
            PreferenceManager.setDefaultValues(this, R.xml.preference_screen, true);
            mMinDistance = Settings.loadMinDistance(mPreferences);
            zoomLevel = Settings.loadInitialZoomValue(mPreferences);
        } catch (NumberFormatException e) {
            Log.d(TAG, "failed to read configuration, setting all preferences to default values");
            PreferenceManager.setDefaultValues(this, R.xml.preference_screen, true);
            mMinDistance = Settings.loadMinDistance(mPreferences);
            zoomLevel = Settings.loadInitialZoomValue(mPreferences);
        }
        // TODO: get zoom from preferences ?
        mMapController.setZoom(zoomLevel);
        mPreferences.registerOnSharedPreferenceChangeListener(new OnSharedPreferenceChangeListener() {
            @Override
            public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
            String key) {
                if (key.equals(Settings.MIN_DISTANCE_BETWEEN_POINTS)) {
                    mMinDistance = Settings.loadMinDistance(sharedPreferences);
                }
            }
        });

        mGpsServiceController = new LocationServiceController(this);

        if (isDrawingPath()) {
            mGpsServiceController.startService();
        }

        setAnimateToCurrentPoint(true);

        toastView = new ToastViewHolder();
        toastView.toastRoot = getLayoutInflater().inflate(
                                  R.layout.marker_toast,
                                  (ViewGroup) findViewById(R.id.marker_toast_layout_root));
        toastView.markerImage = (ImageView) toastView.toastRoot.findViewById(R.id.marker_label);
        toastView.markerTitle = (TextView) toastView.toastRoot.findViewById(R.id.marker_title);
        toastView.markerSnippet = (TextView) toastView.toastRoot.findViewById(R.id.marker_snippet);
    }
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);

        // call setter because we also need to animate map
        setAnimateToCurrentPoint(savedInstanceState.getBoolean(ANIMATE_TO_CURRENT_POINT_KEY));
        if (savedInstanceState.containsKey(CENTRAL_POINT_LATITUDE_KEY) &&
                savedInstanceState.containsKey(CENTRAL_POINT_LONGITUDE_KEY)) {
            int latitudeE6 = savedInstanceState.getInt(CENTRAL_POINT_LATITUDE_KEY);
            int longitudeE6 = savedInstanceState.getInt(CENTRAL_POINT_LONGITUDE_KEY);
            mCentralPoint = new GeoPoint(latitudeE6, longitudeE6);
        }

        // copy-paste from MapDisplay
        Marker current = savedInstanceState.getParcelable("com.personalkeeper.currentpoint");
        if (current != null) {
            current.setAssotiateContext(this);
            currentPointMarker.set(current);
        }
        ArrayList<Marker> markers =
            savedInstanceState.getParcelableArrayList("com.personalkeeper.displayedpath");
        if (markers != null) {
            for (Marker marker: markers)
                marker.setAssotiateContext(this);
            pathHolder.clear();
            pathHolder.addRange(markers);
        }
        boolean isReset = savedInstanceState.getBoolean("com.personalkeeper.pathisreset");
        pathHolder.reset(isReset);
    }
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean(ANIMATE_TO_CURRENT_POINT_KEY, mAnimateToCurrentPoint);
        if (mCentralPoint != null) {
            outState.putInt(CENTRAL_POINT_LATITUDE_KEY, mCentralPoint.getLatitudeE6());
            outState.putInt(CENTRAL_POINT_LONGITUDE_KEY, mCentralPoint.getLongitudeE6());
        }

        // copy-paste from MapDisplay
        Marker marker = (currentPointMarker.isEmpty()) ? null : currentPointMarker.get();
        outState.putParcelable("com.personalkeeper.currentpoint", marker);
        outState.putParcelableArrayList("com.personalkeeper.displayedpath", pathHolder.getItems());
        outState.putBoolean("com.personalkeeper.pathisreset", pathHolder.isReset());
    }

    @Override
    protected void onResume() {
        super.onResume();
        try {
        mGpsServiceController.bindService();
        } catch (Exception e) {}
    }
    @Override
    protected void onPause() {
        super.onPause();
        try {
        mGpsServiceController.unbindService();
        } catch (Exception e) {}
    }

    // interfaces methods
    @Override
    public void onMarkerTap(Marker marker) {
        try {
            toastView.markerImage.setImageDrawable(marker.getMarker(0));
            toastView.markerTitle.setText(marker.getTitle());
            toastView.markerSnippet.setText(marker.getSnippet());
            Toast toast = new Toast(this);
            toast.setDuration(Toast.LENGTH_SHORT);
            toast.setView(toastView.toastRoot);
            toast.show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    
    @Override
    public void onLocationUpdate(Location location) {
        Log.d(TAG, "location update");
        if (isDrawingPath()) {
            if (mLastUpdatedLocation == null ||
                    Utils.distanceBetween(mLastUpdatedLocation, location) > mMinDistance) {
                mLastUpdatedLocation = location;

                pathHolder.updateLastPoint();
                pathHolder.addPoint(location);
                Log.d(TAG, "points: " + pathHolder.getItems().size());
            } else {
                // mLastUpdatedLocation is guaranteed not to be null
                mLastUpdatedLocation.setTime(location.getTime());
            }
        } else
            mLastUpdatedLocation = location;

        // always set current point
        currentPointMarker.set(createCurrentMarker(
                                   location.getLatitude(),
                                   location.getLongitude(),
                                   location.getTime()));
        accuracyOverlay.setLocation(location);

        getMap().invalidate();

        if (isAnimateToCurrentPoint())
            animateToPoint(location);
    }

    private Marker createCurrentMarker(double latitude, double longitude, long date) {
        return Utils.createTypicalMarker(this, latitude, longitude, date, Marker.TYPE_CURRENT_POINT);
    }

    // getters, setters
    public Location getLastUpdatedLocation() {
        return mLastUpdatedLocation;
    }
    public GeoPoint getCentralPoint() {
        return mCentralPoint;
    }
    public void setCentralPoint(GeoPoint centralPoint) {
        setAnimateToCurrentPoint(false);
        mCentralPoint = centralPoint;
        animateToPoint(centralPoint);
    }
    public boolean isAnimateToCurrentPoint() {
        return mAnimateToCurrentPoint;
    }
    protected void setAnimateToCurrentPoint(boolean animate) {
        mAnimateToCurrentPoint = animate;
        mCentralPoint = null;
        if (animate && mLastUpdatedLocation != null)
            animateToPoint(mLastUpdatedLocation);
    }
    protected void setDrawingPath(final boolean draw) {
        Editor editor = mPreferences.edit();
        editor.putBoolean(Settings.IS_TRACKING, draw);
        if (isDrawingPath() != draw) {
            if (!draw) {
                pathHolder.reset(true);
                // create copy of current copy and put it into list of passed points
                if (!currentPointMarker.isEmpty())
                    pathHolder.addPoint(currentPointMarker.get().clone());
            } else {
                pathHolder.clear();
                getMap().invalidate();
            }

            if (draw)
                editor.putLong(Settings.TRACKING_START_TIME, System.currentTimeMillis());
            // comming before starting service
            editor.commit();
            try {
                if (draw)
                    mGpsServiceController.startService();
                else
                    mGpsServiceController.stopService();

                mGpsServiceController.rereadConfiguration();
            } catch (IllegalStateException e) {
                Log.d(TAG, "Service is unbound");
            }
        } else
            editor.commit();
    }

    public LocationServiceController getGPSController() {
        return mGpsServiceController;
    }
    public boolean isDrawingPath() {
        return mPreferences.getBoolean(Settings.IS_TRACKING, false);
    }
    protected MapView getMap() {
        return mMap;
    }
    protected MapController getMapController() {
        return mMapController;
    }
    protected List<Overlay> getMapOverlays() {
        return mMapOverlays;
    }
    protected SharedPreferences getPreferences() {
        return mPreferences;
    }

    // just methods
    protected void animateToPoint(Location point) {
        animateToPoint(new GeoPoint((int)(point.getLatitude() * 1E6),
                                    (int)(point.getLongitude() * 1E6)));
    }
    protected void animateToPoint(GeoPoint point) {
        mMapController.animateTo(point);
    }

    // from MapActivity
    @Override
    protected boolean isRouteDisplayed() {
        return false;
    }
}
